/*
 * Copyright (c) 2008, Solido Systems
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of Solido Systems nor the names of its contributors may be
 * used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.solidosystems.objectkitchen.index;

import java.io.*;
import java.util.*;

public class NodeManager{
	private String filename;
	private RandomAccessFile fileHandle;
	private DataOutputStream fileStream;
	private long rootLocation;
	private long fileSize=0;
	
	private HashMap<Long,Node> nodeCache;
	
	public NodeManager(String filename) throws IOException{
		nodeCache=new HashMap<Long,Node>();
		this.filename=filename;
		this.fileHandle=null;
		this.fileStream=null;
		File ftest=new File(filename);
		if(!ftest.exists()){
			createDataFile();
		}else{
			initializeDataFile();
		}
		fileSize=ftest.length();
	}
	
	public Node getRoot() throws IOException{
		return getNode(rootLocation);
	}
	
	public void close() throws IOException{
		if(fileHandle!=null){
			fileHandle.close();
			fileHandle=null;
		}
		if(fileStream!=null){
			fileStream.close();
			fileStream=null;
		}
	}
	
	private long getFileSize() throws IOException{
		// RandomAccessFile file=getFileHandle();
		// return file.length();
		return fileSize;
	}
	
	private RandomAccessFile getFileHandle() throws IOException{
		if(fileHandle!=null)return fileHandle;
		if(fileStream!=null){
			fileStream.close();
			fileStream=null;
		}
		fileHandle= new RandomAccessFile(filename,"rw");
		return fileHandle;
	}
	
	private DataOutputStream getFileStream() throws IOException{
		if(fileStream!=null)return fileStream;
		if(fileHandle!=null){
			fileHandle.close();
			fileHandle=null;
		}
		fileStream=new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filename,true)));
		return fileStream;
	}
	
	private void initializeDataFile() throws IOException{
		fileHandle=new RandomAccessFile(filename,"rw");
		rootLocation=fileHandle.readLong();
		fileHandle.seek(rootLocation);
		Node root=Node.readFromFile(this,fileHandle);
		nodeCache.put(rootLocation,root);
	}
	
	private void createDataFile() throws IOException{
		fileHandle=new RandomAccessFile(filename,"rw");
		Node root=new Node(this,8,-1);
		fileHandle.writeLong(8);
		rootLocation=8;
		root.writeToFile(fileHandle);
		nodeCache.put(new Long(8),root);
	}
	
	private Node findLeaf(int hashCode) throws IOException{
		return getRoot().findLeaf(hashCode);
	}
	
	protected Node createNode(Node parent) throws IOException{
		Node node=new Node(this,getFileSize(),parent.getLocation());
		addNode(node);
		return node;
	}
	
	public IndexEntry findEntry(String id,int hashCode) throws IOException{
		// System.out.println("Looking for "+id+" "+hashCode);
		byte[] buf=id.getBytes("UTF-8");
		IndexEntry entry= getRoot().findEntry(hashCode,buf);
		// if(entry==null)System.out.println(" + Returned null");
		return entry;
	}
	
	public List<IndexEntry> findAll() throws IOException{
		return getRoot().getAll();
	}
	
	public void updateIndexEntry(IndexEntry entry) throws IOException{
		RandomAccessFile file=getFileHandle();
		file.seek(entry.getLocation());
		entry.writeToFile(file);
	}
	
	public void addIndexEntry(String id, int hashCode,int fileId, long location) throws IOException{
		byte[] buf=id.getBytes("UTF-8");
		Node leaf=findLeaf(hashCode);
		// getFileHandle();
		long indexLocation=fileSize;
		IndexEntry entry=new IndexEntry(indexLocation,location,fileId,hashCode,buf);
		DataOutputStream stream=getFileStream();
		int pre=stream.size();
		entry.writeToStream(stream);
		fileSize+=(stream.size()-pre);
		leaf.addIndexEntry(entry);
	}
	
	protected IndexEntry getIndexEntry(long location) throws IOException{
		// if(entryCache.containsKey(location))return entryCache.get(location);
		getFileHandle();
		fileHandle.seek(location);
		IndexEntry entry=IndexEntry.readFromFile(fileHandle);
		return entry;
	}
	
	protected Node getNode(long location) throws IOException{
		Node cnode=null;
		if(nodeCache.containsKey(location)){
			cnode=nodeCache.get(location);
			cnode.markUsage();
			return cnode;
		} 
		getFileHandle();
		fileHandle.seek(location);
		Node node=Node.readFromFile(this,fileHandle);
		nodeCache.put(location,node);
		return node;
	}
	
	protected void saveNode(Node node) throws IOException{
		getFileHandle();
		fileHandle.seek(node.getLocation());
		// System.out.println("Saving node at "+node.getLocation());
		// node.inspectNode();
		node.writeToFile(fileHandle);
		nodeCache.put(node.getLocation(),node);
		// node=getNode(node.getLocation());
		// node.inspectNode();
	}
	
	protected void addNode(Node node) throws IOException{
		// getFileHandle();
		long location=fileSize;
		// fileHandle.seek(location);
		DataOutputStream stream=getFileStream();
		int pre=stream.size();
		node.writeToStream(fileStream);
		fileSize+=stream.size()-pre;
		nodeCache.put(location,node);
	}
}